package org.infodavid.common.impl.services;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IUserGroupListItem;
import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.impl.services.helpers.ServiceHelper;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserGroup;
import org.infodavid.common.model.criteria.IExtendedSearchCriteria;
import org.infodavid.common.model.criteria.IUserGroupCriteria;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.UserDataService;
import org.infodavid.common.persistence.UserGroupDataService;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.UserGroupService;
import org.infodavid.common.services.exception.IllegalOperationException;
import org.infodavid.common.services.exceptions.ServiceException;
import org.infodavid.common.services.validators.IValidator;

/**
 * The Class UserGroupServiceImpl.
 */
@AImplementation(value = UserGroupService.class, runtime = ERuntime.DEFAULT)
public class UserGroupServiceImpl extends UserGroupService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(UserGroupServiceImpl.class);

  /**
   * The Class RemovalValidator.
   */
  protected static class RemovalValidator implements IValidator<IUserGroup> {

    /*
     * See super class or interface. (non-Javadoc)
     * @see
     * org.infodavid.common.services.IValidator#validate(org.infodavid.common.services.IApplicationContext
     * , java.io.Serializable)
     */
    public void validate(final IApplicationContext context, final IUserGroup value)
        throws ServiceException, PersistenceException {
      if (value != null) {
        if (ADMINISTRATORS.equals(value.getName())) {
          throw new IllegalOperationException("Group can not be removed: " + value.getName());
        }

        if (SYSTEM.equals(value.getName())) {
          throw new IllegalOperationException("Group can not be removed: " + value.getName());
        }

        if (OPERATORS.equals(value.getName())) {
          throw new IllegalOperationException("Group can not be removed: " + value.getName());
        }
      }
    }

    /*
     * See super class or interface. (non-Javadoc)
     * @see
     * org.infodavid.common.services.IValidator#validate(org.infodavid.common.services.IApplicationContext
     * , java.util.Collection)
     */
    public void validate(final IApplicationContext context, final Collection<IUserGroup> values)
        throws ServiceException, PersistenceException {
      if (!(values == null || values.isEmpty())) {
        for (final IUserGroup value : values) {
          validate(context, value);
        }
      }
    }
  }

  /** The removal validator. */
  private final IValidator<IUserGroup> removalValidator = new RemovalValidator();

  /** The helper. */
  private final ServiceHelper<IUserGroupListItem,IUserGroup,Long,IUserGroupCriteria> helper;

  /**
   * The Constructor.
   */
  protected UserGroupServiceImpl() {
    super();

    helper =
        new ServiceHelper<IUserGroupListItem,IUserGroup,Long,IUserGroupCriteria>(
            UserGroupDataService.getInstance());
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long add(final IApplicationContext context, final IUserGroup value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);
    final long key = helper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("A new group has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A new group has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.GroupService#findByMember(org.infodavid.common.services.
   * IApplicationContext, long, java.util.List, java.util.List)
   */
  @Override
  @SuppressWarnings("boxing")
  public long findByMember(final IApplicationContext context, final long value,
      final List<IUserGroupListItem> results, final List<IQueryParameter> parameters)
      throws ServiceException, PersistenceException {
    helper.validate(value);
    ServiceUtil.validate(context);
    helper.validate(results);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final IUser user =
        UserDataService.getInstance().findByPrimaryKey(context.getPersistenceSession(), value);

    if (user != null) {
      final IUserGroup primaryGroup = user.getPrimaryGroup();
      final Map<String,IUserGroup> groups = user.getGroups();

      if (groups == null) {
        if (primaryGroup != null) {
          results.add(primaryGroup);
        }
      }
      else {
        if (!groups.containsKey(primaryGroup.getName())) {
          results.add(primaryGroup);
        }

        results.addAll(groups.values());
      }
    }

    postExecute(evt.setResults(results));

    return results.size();
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.GroupService#findByName(org.infodavid.common.services.IApplicationContext
   * , java.lang.String)
   */
  @Override
  public IUserGroup findByName(final IApplicationContext context, final String value)
      throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IUserGroup.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final IUserGroup result =
        ((UserGroupDataService)helper.getDataService()).findByName(context.getPersistenceSession(),
            value);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public IUserGroup get(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final IUserGroup result = helper.get(context, key);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.GroupService#findByGuest(org.infodavid.common.services.
   * IApplicationContext, boolean, java.util.List, java.util.List)
   */
  @SuppressWarnings("boxing")
  @Override
  public long findByGuest(final IApplicationContext context, final boolean flag,
      final List<IUserGroup> entities, final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, flag, criteria);
    final long result =
        ((UserGroupDataService)helper.getDataService()).findByGuest(
            context.getPersistenceSession(), flag, entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.GroupService#addMembers(org.infodavid.common.services.IApplicationContext
   * , long, long[])
   */
  @Override
  @SuppressWarnings("boxing")
  public void addMembers(final IApplicationContext context, final long groupKey,
      final long... members) throws ServiceException, PersistenceException {
    helper.validate(context, groupKey);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, helper.get(context, groupKey));

    ((UserGroupDataService)helper.getDataService()).addMembers(context.getPersistenceSession(),
        groupKey, members);
    postExecute(evt.setResults(helper.get(context, groupKey)));
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.GroupService#removeMembers(org.infodavid.common.services.
   * IApplicationContext, long, long[])
   */
  @Override
  @SuppressWarnings("boxing")
  public void removeMembers(final IApplicationContext context, final long groupKey,
      final long... members) throws ServiceException, PersistenceException {
    helper.validate(context, groupKey);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, helper.get(context, groupKey));

    ((UserGroupDataService)helper.getDataService()).removeMembers(context.getPersistenceSession(),
        groupKey, members);
    postExecute(evt.setResults(helper.get(context, groupKey)));
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.GroupService#findByRole(org.infodavid.common.services.IApplicationContext
   * , org.infodavid.common.model.IGroup.EGroupRole, java.util.List, java.util.List)
   */
  @Override
  public long findByRole(final IApplicationContext context, final EUserRole role,
      final List<IUserGroup> entities, final IExtendedSearchCriteria criteria) throws ServiceException,
      PersistenceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    if (role == null) {
      throw new IllegalArgumentException(IUserGroup.ROLE);
    }

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, role, criteria);
    final long result =
        ((UserGroupDataService)helper.getDataService()).findByRole(context.getPersistenceSession(),
            role, entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findEntities(final IApplicationContext context,
      final List<IUserGroupListItem> entities, final IUserGroupCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((UserGroupDataService)helper.getDataService()).find(context.getPersistenceSession(),
            entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public void remove(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, REMOVE_ACTION, key);
    final IUserGroup existing = helper.get(context, Long.valueOf(key));

    // some groups can not be removed
    removalValidator.validate(context, existing);
    helper.remove(context, existing);
    postExecute(evt.setResults(existing));

    LOGGER.info("A group has been removed (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A group has been removed: " + existing + " (" + key + ')');
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#update(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void update(final IApplicationContext context, final IUserGroup value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, value);

    helper.update(context, value);
    postExecute(evt.setResults(value));

    LOGGER.info("A group has been updated (" + value.getKey() + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A group has been updated: " + value + " (" + value.getKey() + ')');
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.GroupService#isMemberOf(org.infodavid.common.services.IApplicationContext
   * , long, long)
   */
  @Override
  public boolean isMemberOf(final IApplicationContext context, final long user, final long group)
      throws PersistenceException, ServiceException {
    final List<IUserGroupListItem> groups = new ArrayList<IUserGroupListItem>();
    boolean result = false;

    findByMember(context, user, groups, null);

    if (!groups.isEmpty()) {
      final Iterator<IUserGroupListItem> ite = groups.iterator();
      Long key;

      while (ite.hasNext() && !result) {
        key = ite.next().getKey();
        result = key != null && group == key.longValue();
      }
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.GroupService#isMemberOf(org.infodavid.common.services.IApplicationContext
   * , java.lang.String, java.lang.String)
   */
  @Override
  public boolean isMemberOf(final IApplicationContext context, final String userName,
      final String groupName) throws PersistenceException, ServiceException {
    final IUserGroup group = findByName(context, groupName);
    boolean result = false;

    if (!(group == null || group.getKey() == null)) {
      final IUser user =
          UserDataService.getInstance().findByName(context.getPersistenceSession(), userName);

      if (!(user == null || user.getKey() == null)) {
        result = isMemberOf(context, user.getKey().longValue(), group.getKey().longValue());
      }
    }

    return result;
  }
}
